if not Stuf and not StufRaid then return end
local su, sru, srru

local HealComm = LibStub:GetLibrary("LibHealComm-4.0", true)
if not HealComm then return end

local a = CreateFrame("Frame", "Stuf_VisualHeal", UIParent)

local setw, seth, setcoord = PlayerFrameBackground.SetWidth, PlayerFrameBackground.SetHeight, PlayerFrameBackground.SetTexCoord
local normal =  function(this, oval, val, bv) setw(this, (val - oval) * bv) setcoord(this, oval, val, 0, 1) end
local reverse = function(this, oval, val, bv) setw(this, (val - oval) * bv) setcoord(this, 1-val, 1-oval, 0, 1) end
local vnormal =  function(this, oval, val, bv) seth(this, (val - oval) * bv) setcoord(this, val,0, oval,0, val,1, oval,1) end
local vreverse = function(this, oval, val, bv) seth(this, (val - oval) * bv) setcoord(this, 1-oval,0, 1-val,0, 1-oval,1, 1-val,1) end


local sort = sort
local select, pairs, ipairs = select, pairs, ipairs
local gsub = gsub
local GetTime = GetTime
local UnitGUID = UnitGUID

local playerguid
local pcr, pcg, pcb, pca = 0.7, 0.7, 1, 1
local cr, cg, cb, ca = 0.7, 1, 0.7, 1
local rpcr, rpcg, rpcb, rpca = 0.7, 0.7, 1, 1
local rcr, rcg, rcb, rca = 0.7, 1, 0.7, 1
local oh, roh = 1.1, 1.1

local ctime = 0
local function lsort(a, b)
	return ((a.endTime and a.endTime > ctime and a.endTime) or 10000000) < ((b.endTime and b.endTime > ctime and b.endTime) or 10000000)
end
local function OnUpdate(this, a1)
	this.elapsed = (this.elapsed or 0) + a1
	if this.elapsed < 0.06 then return end
	this.elapsed = 0
	
	local it = this.p.incomingheals
	sort(it, lsort)
	if not it[1].endTime then
		return this:Hide()
	end
	
	ctime = GetTime()
	local cache = this.p.cache or this.p
	local frac = cache.frachp or 1
	local rat = (this.healmod or 1) / (cache.maxhp or 100)
	local maxoh = (this.israid and roh) or oh or 1.1
	this.section0:SetValue(0, frac, this.bv)

	for i = 1, 4, 1 do
		local temp = it[i]
		local remain = (temp.endTime or ctime) - ctime
		local section = this["section"..i]
		if remain > 0 then
			local newfrac
			if frac >= maxoh then
				newfrac = frac + 0.001
			else
				newfrac = frac + rat * temp.heal
				newfrac = newfrac < maxoh and newfrac or maxoh
			end
			local cfrac = (remain > 7 and 0) or (remain > 0.5 and (0.75 - 0.1 * remain)) or (1 - 0.5 * remain)
			if playerguid == temp.guid then
				if this.israid then
					section:SetVertexColor(rpcr * cfrac, rpcg * cfrac, rpcb * cfrac, rpca)
				else
					section:SetVertexColor(pcr * cfrac, pcg * cfrac, pcb * cfrac, pca)
				end
			elseif this.israid then
				section:SetVertexColor(rcr * cfrac, rcg * cfrac, rcb * cfrac, rca)
			else
				section:SetVertexColor(cr * cfrac, cg * cfrac, cb * cfrac, ca)
			end
			section:SetValue(frac, newfrac, this.bv)
			frac = newfrac
		else
			section:SetVertexColor(0, 0, 0, 0)
			temp.guid, temp.heal, temp.endTime = nil, nil, nil
			if i == 1 then
				this:Hide()
			end
		end
	end
end


local function healstart(uf, f, fguid, uguid, healerguid, healSize, endTime)
	if f and not f.hidden and uguid == fguid and (not uf.cache or not uf.cache.dead) then
		for i = 1, 4, 1 do
			local t = uf.incomingheals[i]
			if not t.endTime or t.endTime < ctime then
				t.guid, t.heal, t.endTime = healerguid, healSize, endTime
				f.healmod = HealComm:GetHealModifier(fguid)
				f:Show()
				OnUpdate(f, 1)
				return true
			end
		end
	end
end
function a:HealComm_HealStarted(event, healerguid, _, healType, endTime, ...)
	if healType ~= HealComm.DIRECT_HEALS and healType ~= HealComm.CHANNEL_HEALS then return end
	ctime = GetTime()
	local healSize = HealComm:GetCasterHealAmount(healerguid, HealComm.ALL_HEALS, endTime + 0.1)
	if not healSize then return end
	local targets = select('#', ...)
	healSize = healSize / targets
	for i = 1, targets, 1 do
		local uguid = select(i, ...)
		if su then
			for u, uf in pairs(su) do
				healstart(uf, uf.incomingbar, UnitGUID(u), uguid, healerguid, healSize, endTime)
			end
		end
		if sru then
			for u, uf in pairs(sru) do
				if healstart(uf, uf.incomingbar, UnitGUID(u), uguid, healerguid, healSize, endTime) then
					break
				end
			end
		end
		if srru then
			for u, uf in pairs(srru) do
				if healstart(uf, uf.incomingbar, UnitGUID(u), uguid, healerguid, healSize, endTime) then
					break
				end
			end
		end
	end
end

local function healdelay(uf, f, fguid, uguid, healerguid, endTime)
	if f and uguid == fguid then
		for index, value in ipairs(uf.incomingheals) do
			if value.guid == healerguid then
				value.endTime = endTime
				break
			end
		end
	end
end
function a:HealComm_HealDelayed(event, healerguid, _, healType, endTime, ...)
	if healType ~= HealComm.DIRECT_HEALS and healType ~= HealComm.CHANNEL_HEALS then return end
	for i = 1, select('#', ...), 1 do
		local uguid = select(i, ...)
		if su then
			for u, uf in pairs(su) do
				healdelay(uf, uf.incomingbar, UnitGUID(u), uguid, healerguid, endTime)
			end
		end
		if sru then
			for u, uf in pairs(sru) do
				healdelay(uf, uf.incomingbar, UnitGUID(u), uguid, healerguid, endTime)
			end
		end
		if srru then
			for u, uf in pairs(srru) do
				healdelay(uf, uf.incomingbar, UnitGUID(u), uguid, healerguid, endTime)
			end
		end
	end
end

local function healmod(f, fguid, uguid, healModifier)
	if f and uguid == fguid then
		f.healmod = healModifier
	end
end
function a:HealComm_ModifierChanged(event, guid, healModifier)
	if su then
		for u, uf in pairs(su) do
			healmod(uf.incomingbar, UnitGUID(u), guid, healModifier)
		end
	end
	if sru then
		for u, uf in pairs(sru) do
			healmod(uf.incomingbar, UnitGUID(u), guid, healModifier)
		end
	end
	if srru then
		for u, uf in pairs(srru) do
			healmod(uf.incomingbar, UnitGUID(u), guid, healModifier)
		end
	end
end

local function healstop(uf, fguid, uguid, healerguid)
	if uf.incomingheals and uguid == fguid then
		for index, value in ipairs(uf.incomingheals) do
			if value.guid == healerguid then
				value.guid, value.heal, value.endTime = nil, nil, nil
				break
			end
		end
	end
end
function a:HealComm_HealStopped(event, healerguid, _, healType, _, ...)
	if healType ~= HealComm.DIRECT_HEALS and healType ~= HealComm.CHANNEL_HEALS then return end
	for i = 1, select('#', ...), 1 do
		local uguid = select(i, ...)
		if su then
			for u, uf in pairs(su) do
				healstop(uf, UnitGUID(u), uguid, healerguid)
			end
		end
		if sru then
			for u, uf in pairs(sru) do
				healstop(uf, UnitGUID(u), uguid, healerguid)
			end
		end
		if srru then
			for u, uf in pairs(srru) do
				healstop(uf, UnitGUID(u), uguid, healerguid)
			end
		end
	end
end

HealComm.RegisterCallback(a, "HealComm_HealStarted")
HealComm.RegisterCallback(a, "HealComm_HealDelayed")
HealComm.RegisterCallback(a, "HealComm_ModifierChanged")
HealComm.RegisterCallback(a, "HealComm_HealStopped")

a:RegisterEvent("ADDON_LOADED")
a:SetScript("OnEvent", function(this, event, a1)
	if a1 ~= "Stuf_VisualHeal" then return end
	a:UnregisterEvent("ADDON_LOADED")
	if Stuf then
		local function module()
			local sdb = (StufDB == "perchar" and StufCharDB) or StufDB
			local function UpdateIncomingHealBar(unit, uf, f, reset, a5, config)
				uf = uf or su[unit]
				f = f or (uf and uf.incomingbar)
				if not f or f.hidden then return end
				if config then
					uf.incomingheals[1].guid, uf.incomingheals[1].heal, uf.incomingheals[1].endTime = "Blah", (uf.cache.maxhp or 100) * 0.025, GetTime() + 0.7
					uf.incomingheals[2].guid, uf.incomingheals[2].heal, uf.incomingheals[2].endTime = playerguid, (uf.cache.maxhp or 100) * 0.025, GetTime() + 0.7
					uf.incomingheals[3].guid, uf.incomingheals[3].heal, uf.incomingheals[3].endTime = nil, nil, nil
					uf.incomingheals[4].guid, uf.incomingheals[4].heal, uf.incomingheals[4].endTime = nil, nil, nil
					f:SetScript("OnUpdate", nil)
					f:Show()
					OnUpdate(f, 2)
				else
					f:SetScript("OnUpdate", OnUpdate)
					if reset then
						for i = 1, 4, 1 do
							uf.incomingheals[i].guid, uf.incomingheals[i].heal, uf.incomingheals[i].endTime = nil, nil, nil
						end
						f:Hide()
					else
						OnUpdate(f, 2)
					end
				end
			end
			local function CreateIncomingBar(unit, uf, name, db, a5, config)
				local f = uf[name]
				if db.hide then
					if f then
						su = nil
						f.hidden = true
						Stuf:RegisterElementRefresh(uf, "incomingbar", "healthelements", nil)
						f:Hide()
					end
					return
				end
				if not f then
					f = Stuf:CreateBase(unit, uf, name, db)
					f.p = uf
					for i = 0, 4, 1 do
						f["section"..i] = f:CreateTexture(nil, "ARTWORK")
					end
					f:Hide()
					
					uf.incomingheals = { { }, { }, { }, { }, }
					uf.refreshfuncs.incomingbar = UpdateIncomingHealBar
				end
				if db.merge and su[unit].hpbar then
					local dbu = su[unit].hpbar.db
					local cw = dbu.w - (dbu.barinsetleft or 0) - (dbu.barinsetright or 0)
					local ch = dbu.h - (dbu.barinsettop or 0) - (dbu.barinsetbottom or 0)
					db.x, db.y, db.w, db.h = dbu.x + (dbu.barinsetleft or 0), dbu.y - (dbu.barinsettop or 0), cw, ch
					db.reverse, db.vertical = dbu.reverse, dbu.vertical
					db.framelevel = su[unit].hpbar:GetFrameLevel()
					sdb.global.vhcurrent.r, sdb.global.vhcurrent.g, sdb.global.vhcurrent.b, sdb.global.vhcurrent.a = 0, 0, 0, 0
				end
				su = Stuf.units
				Stuf:RegisterElementRefresh(uf, "incomingbar", "healthelements", true)
				Stuf:UpdateBaseLook(uf, f, db)
				f.hidden = nil

				local point, rpoint, w, h, bv, setvalue
				if db.vertical then
					if db.reverse then
						point, rpoint, w, h, bv, setvalue = "TOP", "BOTTOM", db.w, 1, db.h, vreverse
					else
						point, rpoint, w, h, bv, setvalue = "BOTTOM", "TOP", db.w, 1, db.h, vnormal
					end
				else
					if db.reverse then
						point, rpoint, w, h, bv, setvalue = "RIGHT", "LEFT", 1, db.h, db.w, reverse
					else
						point, rpoint, w, h, bv, setvalue = "LEFT", "RIGHT", 1, db.h, db.w, normal
					end
				end
				f.bv = bv
				for i = 0, 4, 1 do
					local section = f["section"..i]
					section:SetTexture(Stuf:GetMedia("statusbar"))
					section:ClearAllPoints()
					if i == 0 then
						section:SetPoint(point, f, point)
					else
						section:SetPoint(point, f["section"..(i - 1)], rpoint)
					end
					section.SetValue = setvalue
					section:SetWidth(w)
					section:SetHeight(h)
				end
				f.section0:SetVertexColor(sdb.global.vhcurrent.r, sdb.global.vhcurrent.g, sdb.global.vhcurrent.b, sdb.global.vhcurrent.a)
				pcr, pcg, pcb, pca = sdb.global.vhyour.r, sdb.global.vhyour.g, sdb.global.vhyour.b, sdb.global.vhyour.a
				cr, cg, cb, ca = sdb.global.vhother.r, sdb.global.vhother.g, sdb.global.vhother.b, sdb.global.vhother.a
				oh = sdb.global.maxoh or 1.06
				
				UpdateIncomingHealBar(unit, uf, f, nil, nil, config)
			end
			Stuf:AddBuilder("incomingbar", CreateIncomingBar)

			local function SetUnitDefault(unit, hide)
				local dbu = sdb[unit]
				dbu.incomingbar = dbu.incomingbar or { hide=hide, x=dbu.hpbar.x, y=dbu.hpbar.y, w=dbu.hpbar.w, h=dbu.hpbar.h, reverse=dbu.hpbar.reverse, vertical=dbu.hpbar.vertical, }
				dbu.incomingtext = nil 
				if not dbu.incomingbar.hide then
					su = Stuf.units
					local uf = su[unit]
					if uf and not uf.incomingbar then
						CreateIncomingBar(unit, uf, "incomingbar", dbu.incomingbar)
					end
				end
			end
			sdb.global.vhcurrent = sdb.global.vhcurrent or { r = 0, g = 0, b = 0, a = 0, }
			sdb.global.vhyour = sdb.global.vhyour or { r = 0.7, g = 0.7, b = 1, a = 1, }
			sdb.global.vhother = sdb.global.vhother or { r = 0.7, g = 1, b = 0.7, a = 1, }
			sdb.global.maxoh = sdb.global.maxoh or 1.06
			
			SetUnitDefault("player", nil)
			SetUnitDefault("target", nil)
			SetUnitDefault("party1", nil)
			SetUnitDefault("focus", true)
			SetUnitDefault("targettarget", true)
			playerguid = UnitGUID("player")
		end  -- end function module
		if Stuf.modules then
			tinsert(Stuf.modules, module)
		else
			module()
			module = nil
		end
	end
	if StufRaid then
		local function module()
			local sdb = (StufRaidDB == "perchar" and StufRaidCharLayoutDB) or StufRaidDB
			local function CreateIncomingBar(uf, layout, name)
				if (layout ~= sdb.raid and layout ~= sdb.raidrole) or name ~= "hpbar" then return end
				local f = uf.incomingbar
				local db = layout.hpbar
				if layout.incomingbar.hide or db.hide then
					if f then
						f:Hide()
						f.hidden = true
						if layout == sdb.raid then
							sru = nil
						else
							srru = nil
						end
					end
					return
				end
				if not f then
					f = CreateFrame("Frame", nil, uf)
					f.p = uf
					f.israid = true
					for i = 0, 4, 1 do
						f["section"..i] = f:CreateTexture(nil, "ARTWORK")
					end
					f:Hide()
					uf.incomingbar = f
					uf.incomingheals = { { }, { }, { }, { }, }
				end
				local cw = db.w - (db.barinsetleft or 0) - (db.barinsetright or 0)
				local ch = db.h - (db.barinsettop or 0) - (db.barinsetbottom or 0)
				f:SetFrameLevel(uf.hpbar:GetFrameLevel())
				f:SetWidth(cw)
				f:SetHeight(ch)
				f:SetAlpha(db.alpha or 1)
				f:SetPoint("TOPLEFT", uf.hpbar, "TOPLEFT", db.barinsetleft or 0, db.barinsettop or 0)
				f:SetScript("OnUpdate", OnUpdate)
				f.hidden = nil
				if layout == sdb.raid then
					sru = StufRaid.raid.units
				else
					srru = StufRaid.raidrole.units
				end

				local point, rpoint, w, h, bv, setvalue
				if db.vertical then
					if (db.reverse and not db.fill) or (not db.reverse and db.fill) then
						point, rpoint, w, h, bv, setvalue = "TOP", "BOTTOM", cw, 1, ch, vreverse
					else
						point, rpoint, w, h, bv, setvalue = "BOTTOM", "TOP", cw, 1, ch, vnormal
					end
				else
					if (db.reverse and not db.fill) or (not db.reverse and db.fill) then
						point, rpoint, w, h, bv, setvalue = "RIGHT", "LEFT", 1, ch, cw, reverse
					else
						point, rpoint, w, h, bv, setvalue = "LEFT", "RIGHT", 1, ch, cw, normal
					end
				end
				f.bv = bv
				for i = 0, 4, 1 do
					local section = f["section"..i]
					section:SetTexture(StufRaid.texture)
					section:ClearAllPoints()
					if i == 0 then
						section:SetPoint(point, f, point)
					else
						section:SetPoint(point, f["section"..(i - 1)], rpoint)
					end
					section.SetValue = setvalue
					section:SetWidth(w)
					section:SetHeight(h)
				end
				f.section0:SetVertexColor(0, 0, 0, 0)
				rpcr, rpcg, rpcb, rpca = sdb.vhyour.r, sdb.vhyour.g, sdb.vhyour.b, sdb.vhyour.a
				rcr, rcg, rcb, rca = sdb.vhother.r, sdb.vhother.g, sdb.vhother.b, sdb.vhother.a
				roh = sdb.maxoh or 1.02
			end
			
			local osrcb = StufRaid.CreateBar
			StufRaid.CreateBar = function(uf, layout, name)
				osrcb(uf, layout, name)
				CreateIncomingBar(uf, layout, name)
			end

			sdb.vhyour = sdb.vhyour or { r = 0.7, g = 0.7, b = 1, a = 1, }
			sdb.vhother = sdb.vhother or { r = 0.7, g = 1, b = 0.7, a = 1, }
			sdb.maxoh = sdb.maxoh or 1.02
			
			sdb.raid.incomingbar = sdb.raid.incomingbar or { }
			sdb.raidrole.incomingbar = sdb.raidrole.incomingbar or { }
			
			if not sdb.raid.incomingbar.hide then
				sru = StufRaid.raid.units
				for index, gf in pairs(StufRaid.raid.groups) do
					for i = 1, 40, 1 do
						local uf = gf:GetAttribute("child"..i)
						if not uf then break end
						if not uf.incomingbar then
							CreateIncomingBar(uf, sdb.raid, "hpbar")
						end
					end
				end
			end
			if not sdb.raidrole.incomingbar.hide and not sdb.raidrole.group.hide then
				srru = StufRaid.raidrole.units
				for i = 1, 40, 1 do
					local uf = StufRaid.raidrole.group:GetAttribute("child"..i)
					if not uf then break end
					if not uf.incomingbar then
						CreateIncomingBar(uf, sdb.raidrole, "hpbar")
					end
				end
			end
			playerguid = UnitGUID("player")
		end
		if StufRaid.modules then
			tinsert(StufRaid.modules, module)
		else
			module()
			module = nil
		end
	end
end)